#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2023/12/16 15:18
# @Author  : 新猿异码
# @Site    : https://gitee.com/chshao/autotest
# @CSDN    : https://blog.csdn.net/m0_37576542?type=blog
# @File    : device.py
# @Desc    : 模块功能描述
# @Software: PyCharm
import os
import re
import time
import json
import copy
import socket
import datetime
from typing import Union
from autotest.buildin.PopenOS import Popen
from autotest.kernel.logger.AutoLogging import Log

from autotest.module.android.adb import AdbClient
from autotest.module.android.api import UiObjectApi
from autotest.module.android.minicap import Minicap
from autotest.module.android.decorator import except_wrapper
from autotest.module.android.decorator import log_decorator

PKG_ROOT_NAME = "com.example.autotest"
PKG_UI_NAME = "com.example.autotest.ui"
TAG_UI = "UiAutomator"
TAG_WIFI = "WifiManager"
TAG_STUB = "Stub"

PORT10086 = 10086
PORT12306 = 12306
ip_pattern = re.compile('\d+.\d+.\d+.\d+')

WAIT_TIME = 0.5


class Rect(object):
    left = 0
    right = 0
    top = 0
    bottom = 0
    center_x = 0
    center_y = 0

    def __init__(self, left, right, top, bottom):
        self.left = left
        self.right = right
        self.top = top
        self.bottom = bottom
        self.center_x = left + int(0.5 * (right - left))
        self.center_y = top + int(0.5 * (bottom - top))

    def __str__(self):
        return f"Rect({self.left},{self.top},{self.right},{self.bottom})"


class UiClient(object):
    gSocket = None

    def __init__(self, addr: Union[None, str, tuple]):
        # addr:手机sn号或者(ip,port)
        self.addr = addr

    def connect(self):
        """
        @summary:PC连接手机
        @attention :
        """
        # pc与手机通过WiFi通信
        if isinstance(self.addr, tuple):
            return self._connect_wifi(self.addr[0], self.addr[1])
        # pc与手机通过usb通信
        return self._connect_usb(self.addr)

    def _connect_usb(self, serial):
        adb_client = AdbClient()
        adb_client.must_one_device(serial)
        return self._check_and_forward(adb_client, serial)

    def _check_and_forward(self, adb_client: AdbClient, serial=None):
        forward_list = adb_client.forward_list(serial)
        for forwardItem in forward_list:
            if forwardItem.serial == serial:
                port = forwardItem.local.split(':')[-1]
                if port != PORT10086:
                    continue
                return self._connect_wifi('127.0.0.1', port)
        # 通过adb在pc上创建一个server监听10086端口，并将10086端口收到的数据转发给手机server的12306端口
        adb_client.forward(serial, PORT10086, PORT12306)
        # 创建一个socket客户端连接10086端口
        return self._connect_wifi('127.0.0.1', PORT10086)

    def _connect_wifi(self, host, port):
        self.gSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.gSocket.settimeout(5)
        if self.gSocket.connect_ex((host, int(port))) == 0:
            return True
        return False

    @except_wrapper
    def send_cmd(self, cmd):
        cmd_bytes = json.dumps(cmd).encode('utf-8')
        cmd_len = len(cmd_bytes).to_bytes(4, "little")
        self.gSocket.sendall(cmd_len + cmd_bytes)
        return True

    @except_wrapper
    def recv_ack(self):
        # 取前4位包头，为数据内容长度
        dataLenBytes = self.gSocket.recv(4)
        if dataLenBytes != b"":
            dataLen = int.from_bytes(dataLenBytes, "little")
            # 取出数据内容并返回
            return self.gSocket.recv(dataLen)
        return b""

    def close(self):
        if self.gSocket:
            self.gSocket.close()


class Widget(object):

    def __init__(self, rpc, resourceId=None, text=None, className=None, description=None, matchFlag=False, parent=None,
                 mode="ui2"):
        self.__rpc = rpc
        self.resourceId = resourceId
        self.text = text
        self.className = className
        self.description = description
        self.matchFlag = matchFlag
        self.parent = parent
        self.brother_dict = {}
        UiObjectApi.mode = mode

    def __str__(self):
        params_list = [f"{key}={getattr(self, key)}" for key in ["text", "className", "description", "resourceId"] if
                       getattr(self, key)]
        brother_params = ""
        if self.brother_dict:
            brother_params = f"brother({', '.join([k+'='+v for k, v in self.brother_dict.items()])})."
        if isinstance(self.parent, Widget):
            return f"{self.parent.__str__()}child({', '.join(params_list)}).{brother_params}"
        return f"{self.parent.__str__()}widget({', '.join(params_list)}).{brother_params}"

    def __format_params(self, **kwargs):
        parent = self
        param_before = {}
        while parent is not None and isinstance(parent, Widget):
            cur_params = {}
            if parent.resourceId: cur_params.update({"resourceId": parent.resourceId})
            if parent.className: cur_params.update({"className": parent.className})
            if parent.text: cur_params.update({"text": parent.text})
            if parent.description: cur_params.update({"description": parent.description})
            if parent.matchFlag: cur_params.update({"matchFlag": parent.matchFlag})
            if parent.brother_dict: cur_params.update({"brother": parent.brother_dict})
            if param_before:
                cur_params["child"] = param_before
            else:
                param_before.update(cur_params)
            param_before = copy.deepcopy(cur_params)
            parent = getattr(parent, "parent", None)
        param_before.update(kwargs)
        return param_before

    def execute_cmd(self, pkg_name, cls_name, method, args=None):
        cmd = {"pkg": pkg_name,
               "class": cls_name,
               "method": method,
               "args": args,
               "requestId": "request_" + str(round(time.time(), 3))}
        self.__rpc.send_cmd(cmd)
        return self.__rpc.recv_ack()

    def __check_result(self, ack, expect=None) -> bool:
        if ack == b"" or ack is False:
            Log.error("uiautomator has no response!")
            return False
        ack_json = json.loads(ack.decode('utf-8'))
        ack_result = ack_json.get("result")
        if ack_json.get("status") != "ok":
            Log.error(f"uiautomator response error: {ack_result}")
            return False
        if expect is None:
            return True
        if ack_result != expect:
            Log.error(f"expect {expect}, but uiautomator response {ack_result}")
            return False
        return True

    def __get_result(self, ack):
        if ack == b"":
            Log.error("uiautomator has no response!")
            return ""
        return json.loads(ack.decode('utf-8')).get("result")

    def child(self, resourceId=None, text=None, className=None, description=None, matchFlag=False):
        return Widget(self.__rpc, resourceId, text, className, description, matchFlag, self)

    def brother(self, resourceId=None, text=None, className=None, description=None, matchFlag=False):
        if resourceId: self.brother_dict.update({"resourceId": resourceId})
        if className: self.brother_dict.update({"className": className})
        if text: self.brother_dict.update({"text": text})
        if description: self.brother_dict.update({"description": description})
        if matchFlag: self.brother_dict.update({"matchFlag": matchFlag})
        return self

    @log_decorator()
    def get_children(self):
        pass

    @log_decorator()
    def click(self, wait_time=WAIT_TIME) -> bool:
        """
        点击当前对象
        :param wait_time: 点击后等待时长
        :return: True or False
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.click("true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        time.sleep(wait_time)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def long_click(self, wait_time=WAIT_TIME) -> bool:
        """
        长按当前对象
        :param wait_time: 点击后等待时长
        :return: True or False
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.long_click("true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        time.sleep(wait_time)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def click_wait_new_window(self, wait_time=WAIT_TIME) -> bool:
        """
        点击当前对象并等待新窗口打开
        :param wait_time: 点击后等待时长
        :return: True or False
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.click_wait_new_window("true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        time.sleep(wait_time)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def drag(self, end_x: int, end_y: int, steps: int = 5, wait_time=WAIT_TIME) -> bool:
        """
        拖动当前对象
        :param end_x: 终点x位置
        :param end_y: 终点y位置
        :param steps: 拖动速度
        :param wait_time: 拖动完成后等待时长
        :return:
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.drag(end_x, end_y, steps, "true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        time.sleep(wait_time)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def swipe_up(self, steps: int = 5, wait_time=WAIT_TIME) -> bool:
        """
        拖动对象往上滑
        :param steps: 滑动速度
        :param wait_time: 拖动后等待时长
        :return:
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.swipe_up(steps, "true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        time.sleep(wait_time)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def swipe_down(self, steps: int = 5, wait_time=WAIT_TIME) -> bool:
        """
        拖动对象往下滑
        :param steps: 滑动速度
        :param wait_time: 拖动后等待时长
        :return:
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.swipe_down(steps, "true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        time.sleep(wait_time)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def swipe_left(self, steps: int = 5, wait_time=WAIT_TIME) -> bool:
        """
        拖动对象往左滑
        :param steps: 滑动速度
        :param wait_time: 拖动后等待时长
        :return:
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.swipe_left(steps, "true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        time.sleep(wait_time)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def swipe_right(self, steps: int = 5, wait_time=WAIT_TIME) -> bool:
        """
        拖动对象往右滑
        :param steps: 滑动速度
        :param wait_time: 拖动后等待时长
        :return:
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.swipe_right(steps, "true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        time.sleep(wait_time)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def get_text(self) -> str:
        """
        获取对象的文本属性中的文本
        :return:
        """
        method_call, ui_method, ui_method_args = UiObjectApi.get_text()
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        return self.__get_result(ack)

    @log_decorator()
    def get_class_name(self) -> str:
        """
        获得对象类名属性的类名
        :return:
        """
        method_call, ui_method, ui_method_args = UiObjectApi.get_class_name()
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        return self.__get_result(ack)

    @log_decorator()
    def get_description(self) -> str:
        """
        获得对象的描述属性的描述文本
        :return:
        """
        method_call, ui_method, ui_method_args = UiObjectApi.get_description()
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        return self.__get_result(ack)

    @log_decorator()
    def set_text(self, text, wait_time=WAIT_TIME) -> bool:
        """
        在对象中输入文本
        :param text: 文本内容
        :param wait_time: 输入后的等待时长
        :return:
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.set_text(text, "true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        time.sleep(wait_time)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def clear_text(self, wait_time=WAIT_TIME) -> bool:
        """
        清除编辑框文本
        :param wait_time: 清除后等待时长
        :return:
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.clear_text("true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        time.sleep(wait_time)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def is_selected(self) -> bool:
        """
        检查对象的selected属性是否为true
        :return:
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.is_selected("true", "true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def is_checkable(self) -> bool:
        """
        检查对象的checkable属性是否为true
        :return:
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.is_checkable("true", "true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def is_enabled(self) -> bool:
        """
        检查对象的enable属性是否为true
        :return:
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.is_enabled("true", "true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def is_clickable(self) -> bool:
        """
        检查对象的clickable属性是否为true
        :return:
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.is_clickable("true", "true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def is_focused(self) -> bool:
        """
        检查对象的focusable属性是否为true
        :return:
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.is_focused("true", "true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def is_scrollable(self) -> bool:
        """
        检查对象的scrollable属性是否为true
        :return:
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.is_scrollable("true", "true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def get_package_name(self) -> str:
        """
        获取对象包名属性的包名文件
        :return:
        """
        method_call, ui_method, ui_method_args = UiObjectApi.get_package_name()
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        return self.__get_result(ack)

    @log_decorator()
    def get_visible_bounds(self) -> Rect:
        """
        获得当前控件可见区域
        :return:
        """
        method_call, ui_method, ui_method_args = UiObjectApi.get_visible_bounds()
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        rect = self.__get_result(ack)
        left, top, right, bottom = rect.split(",")
        return Rect(int(left), int(right), int(top), int(bottom))

    @log_decorator()
    def get_bounds(self) -> Rect:
        """
        获取对象的矩形坐标，即矩形左上角和右下角坐标
        :return:
        """
        method_call, ui_method, ui_method_args = UiObjectApi.get_bounds()
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        rect = self.__get_result(ack)
        left, top, right, bottom = rect.split(",")
        return Rect(int(left), int(right), int(top), int(bottom))

    @log_decorator()
    def wait_for_exists(self, timeout=5) -> bool:
        """
        等待控件出现
        :param timeout: 等待超时时间
        :return:
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.wait_for_exists(timeout, "true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def wait_until_gone(self, timeout=5) -> bool:
        """
        等待控件消失
        :param timeout: 等待超时时间
        :return:
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.wait_until_gone(timeout, "true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def exists(self) -> bool:
        """
        判断控件是否存在
        :return:
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.exists("true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def pinch_out(self, percent: int, steps: int = 5, wait_time=WAIT_TIME) -> bool:
        """
        手指操作，两点向外扩展
        :param percent: 扩展比例，取值1-100
        :param steps: 滑动速度，该值越小，滑动的速度越快，最小值为2
        :param wait_time: 完成扩展后等待时长
        :return:
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.pinch_out(percent, steps, "true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        time.sleep(wait_time)
        return self.__check_result(ack, expect_ack)

    @log_decorator()
    def pinch_in(self, percent: int, steps: int = 5, wait_time=WAIT_TIME) -> bool:
        """
        手指操作，两点向内收缩
        :param percent: 收缩比例，取值1-100
        :param steps: 滑动速度，该值越小，滑动的速度越快，最小值为2
        :param wait_time: 完成收缩后等待时长
        :return:
        """
        method_call, ui_method, ui_method_args, expect_ack = UiObjectApi.pinch_in(percent, steps, "true")
        method_json = {"method": {"name": ui_method, "args": ui_method_args}}
        args = [{"k": "json", "v": self.__format_params(**method_json)}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, method_call, args)
        time.sleep(wait_time)
        return self.__check_result(ack, expect_ack)


class Device(object):

    def __init__(self, address: Union[str, tuple] = None, mode="ui2"):
        """
        :param address: None or 设备SN or (host, port)
        :param mode: ui2表示使用uiautomator2、ui1表示使用uiautomator1，建议使用ui2模式
        """
        self.mode = mode
        self.ui = UiClient((address[0], PORT12306)) if isinstance(address, tuple) else UiClient(address)

        self.adb = AdbClient()
        if isinstance(address, tuple):
            self.adb.connect_tcp(*address)
            address = ":".join([str(addr) for addr in address])
        self.sn = self.adb.must_one_device(address)

    def __str__(self):
        return f"{self.__class__.__name__}(SN={self.sn})."

    @log_decorator()
    def connect(self):
        # 安装uiautomator应用程序
        if not self.app_check_exist("com.example.autotest"):
            apk_path = os.path.dirname(__file__)
            self.app_install(os.path.join(apk_path, "uiautomator.apk"))
            self.app_install(os.path.join(apk_path, "uiautomator_test.apk"))
            self.__start_uiautomator()
        if re.match(ip_pattern, self.sn):
            # 通过WiFi连接，必须重启uiatomator服务，否则概率报错: UiAutomation not connected，原因暂时未知
            self.__restart_uiautomator()
            Log.info(f"{self.sn} uiautomator connect succeeded!")
            return True
        # 通过USB连接，先尝试连接uiautomator服务
        if self.__check_uiautomator_server_ok() is False:
            self.__restart_uiautomator()
        Log.info(f"{self.sn} uiautomator connect succeeded!")
        return True

    def __start_uiautomator(self):
        """
        启动uiautomator服务
        :return: None
        """
        stub = f"{PKG_ROOT_NAME}.Stub"
        runner = f"{PKG_ROOT_NAME}.test/androidx.test.runner.AndroidJUnitRunner"
        p = Popen(f"adb -s {self.sn} shell am instrument -w -r -e debug false -e class {stub} {runner}",
                  daemonic=True)
        p.wait_until("INSTRUMENTATION_STATUS_CODE:")  # 启动成功标志[INSTRUMENTATION_STATUS_CODE: 1]
        if self.app_pid(PKG_ROOT_NAME, 10) == -1:
            raise EnvironmentError(f"{self.sn} could not start {PKG_ROOT_NAME}")

    def __restart_uiautomator(self):
        """
        重新启动uiautomator服务，并尝试连接
        :return: None
        """
        self.app_stop(PKG_ROOT_NAME)
        time.sleep(1)
        self.__start_uiautomator()
        # 连接uiautomator服务
        if not self.ui.connect():
            raise ConnectionError(f"{self.sn} uiautomator connect failed!")
        if not self.initialize():
            raise Exception(f"{self.sn} initialize {TAG_UI} failed!")

    def execute_cmd(self, pkg_name, cls_name, method, args=None):
        cmd = {"pkg": pkg_name,
               "class": cls_name,
               "method": method,
               "args": args,
               "requestId": "request_" + str(round(time.time(), 3))}
        self.ui.send_cmd(cmd)
        return self.ui.recv_ack()

    def __check_result(self, ack, expect=None) -> bool:
        if ack == b"":
            Log.error(f"{self.ui.addr} uiautomator has no response!")
            return False
        ack_json = json.loads(ack.decode('utf-8'))
        ack_result = ack_json.get("result")
        if ack_json.get("status") != "ok":
            Log.error(f"{self.ui.addr} uiautomator response error: {ack_result}")
            return False
        if expect is None:
            return True
        if ack_result != expect:
            Log.error(f"{self.ui.addr} expect {expect}, but uiautomator response {ack_result}")
            return False
        return True

    def __get_result(self, ack):
        if ack == b"":
            Log.error(f"{self.ui.addr} uiautomator has no response!")
            return ""
        return json.loads(ack.decode('utf-8')).get("result")

    def __check_uiautomator_server_ok(self):
        if not self.ui.connect():
            return False
        if not self.initialize():
            return False
        args = [{"k": "int", "v": 0}, {"k": "int", "v": 0}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "click", args)
        if "UnknownError" in ack.decode('utf-8'):
            Log.error(f"{self.sn} UiAutomation not connected!")
            return False
        return True

    @except_wrapper
    def initialize(self):
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "init", [])
        return self.__check_result(ack, "UiAutomator")

    @log_decorator()
    def app_install(self, apk) -> bool:
        """
        安装软件
        :param apk: apk文件绝对路径
        :return: True or False
        """
        p = Popen(f"adb -s {self.sn} install -r -t -g {apk}", daemonic=True)
        if p.read_until("success", timeout=10)[0]:
            return True
        return False

    @log_decorator()
    def app_uninstall(self, pkg) -> bool:
        """
        卸载软件
        :param pkg: 软件包名
        :return: True or False
        """
        Popen(f"adb -s {self.sn} uninstall {pkg}", daemonic=True).wait_until_end()
        return not self.app_check_exist(pkg)

    def app_check_exist(self, pkg) -> bool:
        """
        检查软件是否已安装
        :param pkg: 软件包名
        :return: True or False
        """
        pkg_list = self.shell(f"pm list packages | grep '{pkg}'").split("\n")
        for package in pkg_list:
            pkg_real = package.split(":")[-1].strip("\n")
            if pkg == pkg_real:
                return True
        Log.error(f"has no pkg {pkg}")
        return False

    @log_decorator()
    def app_pid(self, pkg, timeout=5) -> int:
        """
        获取某应用程序的进程号
        :param pkg: 应用包名
        :param timeout: 超时时间
        :return:
        """
        end_time = time.time() + timeout
        while end_time > time.time():
            pid = self.shell(f"pidof {pkg}").strip("\n")
            if pid:
                return int(pid)
            time.sleep(1)
        return -1

    @log_decorator()
    def app_start(self, pkg, activity=None):
        pass

    @log_decorator()
    def app_stop(self, pkg):
        """
        强制关闭某应用软件
        :param pkg: 应用包名
        :return: None
        """
        self.shell(f"am force-stop {pkg}")

    @log_decorator()
    def app_clear(self, pkg):
        """
        从后台清除应用软件
        :param pkg: 软件包名
        :return: None
        """
        self.shell(f"pm clear {pkg}")

    @log_decorator()
    def pull(self, remote, local) -> str:
        """
        从手机导出文件资源
        :param remote: 手机上的文件资源
        :param local: 导出的目标路径
        :return:
        """
        p = Popen(f"adb -s {self.sn} pull {remote} {local}", daemonic=True)
        return "".join(p.read_until_end()[1])

    @log_decorator()
    def push(self, local, remote) -> str:
        """
        将本地的文件资源导入手机
        :param local: 本地文件资源
        :param remote: 导入到手机的目标路径
        :return:
        """
        p = Popen(f"adb -s {self.sn} push {local} {remote}", daemonic=True)
        return "".join(p.read_until_end()[1])

    def shell(self, cmd) -> str:
        """
        执行adb shell命令
        :param cmd: 具体命令
        :return:
        """
        return self.adb.shell(self.sn, cmd)

    @log_decorator()
    def press(self, key: str, times=1, wait_time=WAIT_TIME) -> bool:
        """
        模拟手机按键
        :param key: back,home,enter,delete,search,menu,volume_up（音量+），volume_down（音量-)，volume_mute（静音）,power
        :param times: 按键次数
        :param wait_time: 等待时间
        :return:
        """
        if key.lower() not in ('back', 'home', 'enter', 'delete', 'search', 'menu'):
            Log.error("param is wrong!")
            return False
        args = [{"k": "string", "v": key}]
        fail_times = 0
        for _ in range(times):
            ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "press", args)
            if not self.__check_result(ack, "true"):
                fail_times += 1
        if fail_times != 0:
            Log.error(f"{self.ui.addr} press {key} fail {fail_times} times!")
        time.sleep(wait_time)
        return fail_times == 0

    @log_decorator()
    def press_keycode(self, keycode: int, wait_time=WAIT_TIME) -> bool:
        """
        模拟短按键盘代码
        :param keycode: 键盘代码
        :param wait_time: 等待时间
        :return:
        """
        args = [{"k": "int", "v": keycode}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "press_keycode", args)
        time.sleep(wait_time)
        return self.__check_result(ack, "true")

    @log_decorator()
    def press_recent_apps(self, wait_time=WAIT_TIME) -> bool:
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "press_recent_apps", [])
        time.sleep(wait_time)
        return self.__check_result(ack, "true")

    @log_decorator()
    def send_keys(self, text, wait_time=WAIT_TIME) -> bool:
        """
        在焦点处于某文本框时，输入文本
        :param text: 待输入的文本内容，使用ADBKeyBoard.apk来支持中文输入，下载地址https://github.com/senzhk/ADBKeyBoard
        :param wait_time: 等待时间
        :return:
        """
        # 先获取当前默认输入法
        default = self.shell("settings get secure default_input_method").strip("\n")
        switch_flag = (default != "com.android.adbkeyboard/.AdbIME")
        if switch_flag:
            # 切换至ADBKeyBoard输入法
            self.shell(f"settings put secure default_input_method com.android.adbkeyboard/.AdbIME")
            time.sleep(0.5)
        p = Popen(f"adb -s {self.sn} shell am broadcast -a ADB_INPUT_TEXT --es msg '{text}'", daemonic=True)
        switch_ack = "".join(p.read_until_end()[1])
        if switch_flag:
            # 最后切回默认输入法
            self.shell(f"settings put secure default_input_method {default}")
        if "Broadcast completed: result=0" in switch_ack:
            time.sleep(wait_time)
            return True
        return False

    @log_decorator()
    def set_input_method(self, name) -> bool:
        """
        设置设备的输入法
        :param name: 输入法包名
        :return:
        """
        self.shell(f"settings put secure default_input_method {name}")
        time.sleep(0.5)
        cur_method = self.shell("settings get secure default_input_method")
        if name in cur_method:
            Log.info(f"设置输入法{name}成功")
            return True
        Log.error(f"设置输入法{name}失败")
        return False

    @log_decorator()
    def get_default_input_method(self) -> str:
        """
        先获取当前使用的默认输入法
        :return:
        """
        default = self.shell("settings get secure default_input_method")
        return default.strip("\n")

    @log_decorator()
    def get_input_methods(self) -> list:
        """
        获取当前设备有效的输入法列表
        :return:
        """
        methods = self.shell("ime list -s")
        return [method for method in methods.split("\n") if method]

    @log_decorator()
    def screen_on(self, wait_time=WAIT_TIME) -> bool:
        """
        点亮屏幕
        :param wait_time: 等待时间
        :return:
        """
        self.execute_cmd(PKG_UI_NAME, TAG_UI, "screen_on", [])
        time.sleep(wait_time)
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "is_screen_on", [])
        return self.__check_result(ack, "true")

    @log_decorator()
    def screen_off(self, wait_time=WAIT_TIME) -> bool:
        """
        熄灭屏幕
        :param wait_time: 等待时间
        :return:
        """
        self.execute_cmd(PKG_UI_NAME, TAG_UI, "screen_off", [])
        time.sleep(wait_time)
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "is_screen_on", [])
        return self.__check_result(ack, "false")

    @log_decorator()
    def is_screen_on(self) -> bool:
        """
        检查屏幕是否亮屏
        :return:
        """
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "is_screen_on", [])
        return self.__check_result(ack, "true")

    @log_decorator()
    def click(self, x, y, wait_time=WAIT_TIME) -> bool:
        """
        按坐标点击（单击）
        :param x: 横坐标
        :param y: 纵坐标
        :param wait_time: 等待时间
        :return:
        """
        args = [{"k": "int", "v": x}, {"k": "int", "v": y}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "click", args)
        time.sleep(wait_time)
        return self.__check_result(ack, "true")

    @log_decorator()
    def double_click(self, x, y, wait_time=WAIT_TIME):
        """
        按坐标点击（双击）
        :param x: 横坐标
        :param y: 纵坐标
        :param wait_time: 等待时间
        :return:
        """
        self.shell(f"input tap {x} {y}")
        self.shell(f"input tap {x} {y}")
        time.sleep(wait_time)

    @log_decorator()
    def long_click(self, x, y, wait_time=WAIT_TIME) -> bool:
        """
        按坐标长按
        :param x: 横坐标
        :param y: 纵坐标
        :param wait_time: 等待时间
        :return:
        """
        return self.swipe(x, y, x, y, 2000, wait_time)

    @log_decorator()
    def drag(self, sx, sy, ex, ey, steps, wait_time=WAIT_TIME) -> bool:
        """
        拖拽
        :param sx: 起始x坐标
        :param sy: 起始y坐标
        :param ex: 终点x坐标
        :param ey: 终点y坐标
        :param steps: 步进值
        :param wait_time: 等待时间
        :return:
        """
        args = [{"k": "int", "v": sx}, {"k": "int", "v": sy},
                {"k": "int", "v": ex}, {"k": "int", "v": ey},
                {"k": "int", "v": steps}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "drag", args)
        time.sleep(wait_time)
        return self.__check_result(ack, "true")

    @log_decorator()
    def swipe(self, sx, sy, ex, ey, steps, wait_time=WAIT_TIME) -> bool:
        """
        滑动屏幕
        :param sx: 起始x坐标
        :param sy: 起始y坐标
        :param ex: 终点x坐标
        :param ey: 终点y坐标
        :param steps: 步进值
        :param wait_time: 等待时间
        :return:
        """
        args = [{"k": "int", "v": sx}, {"k": "int", "v": sy},
                {"k": "int", "v": ex}, {"k": "int", "v": ey},
                {"k": "int", "v": steps}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "swipe", args)
        time.sleep(wait_time)
        return self.__check_result(ack, "true")

    @log_decorator()
    def freeze_rotation(self, wait_time=WAIT_TIME) -> bool:
        """
        禁用传感器和设备的旋转且在当前的旋转状态冻结
        :param wait_time: 等待时间
        :return:
        """
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "freeze_rotation", [])
        time.sleep(wait_time)
        return self.__check_result(ack)

    @log_decorator()
    def unfreeze_rotation(self, wait_time=WAIT_TIME) -> bool:
        """
        重新启用传感器和允许物理旋转
        :param wait_time: 等待时间
        :return:
        """
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "unfreeze_rotation", [])
        time.sleep(wait_time)
        return self.__check_result(ack)

    @log_decorator()
    def get_current_package_name(self) -> str:
        """
        返回当前界面的包名的字符串
        :return:
        """
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "get_current_package_name", [])
        return self.__get_result(ack)

    @log_decorator()
    def get_display_height(self) -> int:
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "get_display_height", [])
        return self.__get_result(ack)

    @log_decorator()
    def get_display_width(self) -> int:
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "get_display_width", [])
        return self.__get_result(ack)

    @log_decorator()
    def get_display_rotation(self) -> int:
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "get_display_rotation", [])
        return self.__get_result(ack)

    @log_decorator()
    def get_product_name(self) -> str:
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "get_product_name", [])
        return self.__get_result(ack)

    @log_decorator()
    def set_orientation_left(self, wait_time=WAIT_TIME) -> bool:
        """
        通过禁用传感器，然后模拟设备向左转，并且固定位置
        :param wait_time: 等待时间
        :return:
        """
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "set_orientation_left", [])
        time.sleep(wait_time)
        return self.__check_result(ack)

    @log_decorator()
    def set_orientation_right(self, wait_time=WAIT_TIME) -> bool:
        """
        通过禁用传感器，然后模拟设置向右转，并且固定冻结在那
        :param wait_time: 等待时间
        :return:
        """
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "set_orientation_right", [])
        time.sleep(wait_time)
        return self.__check_result(ack)

    @log_decorator()
    def set_orientation_natural(self, wait_time=WAIT_TIME) -> bool:
        """
        通过禁用传感器,然后模拟设备转到其自然默认的方向，并且固定位置
        :return:
        """
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "set_orientation_natural", [])
        time.sleep(wait_time)
        return self.__check_result(ack)

    def is_natural_orientation(self):
        """
        检查是否竖屏
        :return:
        """
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "is_natural_orientation", [])
        return self.__get_result(ack) == "true"

    @log_decorator()
    def wait_for_idle(self, timeout) -> bool:
        """
        等待当前应用程序处于空闲状态
        :param timeout: 超时时长，单位毫秒
        :return:
        """
        args = [{"k": "long", "v": timeout}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "wait_for_idle", args)
        return self.__check_result(ack)

    @log_decorator()
    def wait_for_window_update(self) -> bool:
        """
        等待窗口内容更新事件的发生
        :return:
        """
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "wait_for_window_update", [])
        return self.__check_result(ack, "true")

    @log_decorator()
    def take_screenshot(self):
        """
        把当前窗口截图为png格式图片，可以自定义缩放比例与图片质量
        :return:
        """
        pass

    @log_decorator()
    def open_notification(self, wait_time=WAIT_TIME) -> bool:
        """
        打开通知栏
        :return:
        """
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "open_notification", [])
        time.sleep(wait_time)
        return self.__check_result(ack, "true")

    @log_decorator()
    def open_quick_settings(self, wait_time=WAIT_TIME) -> bool:
        """
        打开快速设置
        :return:
        """
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "open_quick_settings", [])
        time.sleep(wait_time)
        return self.__check_result(ack, "true")

    @log_decorator()
    def dump_window_hierarchy(self) -> str:
        """
        获取当前应用界面的布局xml
        :return:
        """
        dump_file = f"/sdcard/AutoTest/dump/dump_{datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S-%f')}.xml"
        args = [{"k": "string", "v": dump_file}]
        self.execute_cmd(PKG_UI_NAME, TAG_UI, "dump_window_hierarchy", args)
        return dump_file

    @log_decorator()
    def set_compressed_layout_hierarchy(self, compressed: bool = True) -> bool:
        args = [{"k": "bool", "v": compressed}]
        ack = self.execute_cmd(PKG_UI_NAME, TAG_UI, "dump_window_hierarchy", args)
        return self.__check_result(ack)

    def widget(self, resourceId=None, text=None, className=None, description=None, matchFlag=False) -> Widget:
        return Widget(self.ui, resourceId, text, className, description, matchFlag, self, self.mode)

    def monkey(self):
        pass

    def logcat(self):
        pass


if __name__ == '__main__':
    d = Device(mode="ui2")
    d.connect()
    # print(d.send_keys("iaiojiojpo回电话"))
    # d.get_product_name()
    # d.get_default_input_method()
    # # d.click(0, 0)
    # d.get_current_package_name()
    # d.press("back")
    # d.press("home")
    # d.widget(className="android.support.v7.widget.RecyclerView") \
    #     .child(resourceId="android:id/summary") \
    #     .brother(resourceId="android:id/title", text="电池") \
    #     .click()
    # d.widget(className="android.support.v7.widget.RecyclerView").child(resourceId="android:id/title", text="电池").click()
    d.widget(text="电池").click()
